package com.hmall.item.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.helper.RabbitMqHelper;
import com.hmall.common.utils.BeanUtils;
import com.hmall.item.domain.dto.ItemDTO;
import com.hmall.item.domain.dto.OrderDetailDTO;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.mapper.ItemMapper;
import com.hmall.item.service.IItemService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

import static com.hmall.common.constants.MQConstants.*;
import static com.hmall.item.constants.ElasticsearchContents.ITEM_INDEX_NAME;

/**
 * <p>
 * 商品表 服务实现类
 * </p>
 *
 * @author 虎哥
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements IItemService {

    @Value("${hm.db.host}")
    private String HOST;

    private RestHighLevelClient client;

    private final RabbitMqHelper rabbitMqHelper;

    /**
     * 初始化es客户端
     */
    @PostConstruct
    public void initElasticsearch() {
        log.info("初始化es客户端");
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create(HOST + ":9200")
        ));
    }


    /**
     * 扣减库存
     *
     * @param items
     */
    @Override
    @Transactional
    public void deductStock(List<OrderDetailDTO> items) {
        String sqlStatement = "com.hmall.item.mapper.ItemMapper.deductStock";
        boolean r = false;
        try {
            r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
        } catch (Exception e) {
            throw new BizIllegalException("扣减库存异常，可能是库存不足!", e);
        }
        if (!r) {
            throw new BizIllegalException("库存不足！");
        }
    }

    /**
     * 根据id批量查询商品
     *
     * @param ids
     * @return
     */
    @Override
    public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
        return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
    }

    /**
     * 恢复库存
     *
     * @param items
     */
    @Override
    @Transactional
    public void restoreStock(List<OrderDetailDTO> items) {
        String sqlStatement = "com.hmall.item.mapper.ItemMapper.restoreStock";
        boolean r = false;
        try {
            r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
        } catch (Exception e) {
            throw new BizIllegalException("恢复库存异常!", e);
        }
        if (!r) {
            throw new BizIllegalException("恢复库存失败！");
        }
    }


    /**
     * 保存商品 到 数据库和索引库
     *
     * @param itemDTO
     */
    @GlobalTransactional
    @Override
    public void saveItem(ItemDTO itemDTO) {
        // 数据库中新增
        Item item = BeanUtils.copyBean(itemDTO, Item.class);
        save(item);    //mp执行插入后自动填充主键
        //主键回显保存
        itemDTO.setId(item.getId());
        // mq异步同步es索引库数据
        log.info("发送消息到mq以同步新增商品数据至es：{}", itemDTO);
        rabbitMqHelper.sendMessage(ITEM_DIRECT_EXCHANGE_NAME, ITEM_SAVE_KEY, itemDTO);
    }


    /**
     * 添加文档 到 索引库
     *
     * @param itemId
     */
    @Override
    public void saveToElasticsearch(Long itemId) {
        // 0.查询数据库获取商品信息
        Item item = getById(itemId);
        ItemDTO itemDTO = BeanUtil.copyProperties(item, ItemDTO.class);
        // 1.创建request对象
        IndexRequest request = new IndexRequest(ITEM_INDEX_NAME).id(itemId.toString());
        // 2.准备request参数
        ItemDoc itemDoc = BeanUtil.copyProperties(itemDTO, ItemDoc.class);
        request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
        // 3.发送请求
        log.info("添加文档到索引库：{}", itemDoc);
        try {
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("添加文档到索引库失败！", e);
        }
    }


    /**
     * 更新商品 到 数据库和索引库
     *
     * @param itemDTO
     */
    @GlobalTransactional
    @Override
    public void updateItem(ItemDTO itemDTO) {
        //更新数据库
        Item item = BeanUtils.copyBean(itemDTO, Item.class);
        updateById(item);
        //发消息更新索引库
        log.info("发送消息到mq以同步更新商品数据至es：{}", itemDTO);
        rabbitMqHelper.sendMessage(ITEM_DIRECT_EXCHANGE_NAME, ITEM_UPDATE_KEY, itemDTO);
    }

    /**
     * 更新索引库
     *
     * @param itemDTO
     */
    @Override
    public void updateToElasticsearch(ItemDTO itemDTO) {
        // 1.判断是否存在
        ItemDTO i = queryByIdFromElasticsearch(itemDTO.getId());
        // 2.存在则删除，不存在则抛异常
        if (i != null) {
            //删除
            deleteToElasticsearch(itemDTO.getId());
        } else {
            throw new RuntimeException(itemDTO.getId() + "商品在更新到es时不存在!");
        }
        // 3.添加新的
        saveToElasticsearch(itemDTO.getId());
    }

    /**
     * 根据id查询商品 从 索引库
     *
     * @param itemId
     */
    @Override
    public ItemDTO queryByIdFromElasticsearch(Long itemId) {
        // 1.创建request对象
        GetRequest request = new GetRequest(ITEM_INDEX_NAME, itemId.toString());
        // 2.发送请求
        GetResponse response;
        try {
            response = client.get(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("根据id从es查询商品失败!", e);
        }
        // 3.解析响应
        if (!response.isExists()) {
            throw new RuntimeException(itemId + "商品不存在!");
        }
        String json = response.getSourceAsString();
        ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
        // 4.封装返回
        return BeanUtil.copyProperties(itemDoc, ItemDTO.class);
    }

    @Override
    public void deleteToElasticsearch(Long itemId) {
        // 1.创建request对象
        DeleteRequest request = new DeleteRequest(ITEM_INDEX_NAME, itemId.toString());
        // 2.发送请求
        try {
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(itemId + "商品根据id删除es商品失败!", e);
        }
    }

    /**
     * 删除商品 从 数据库和索引库
     * @param itemId
     */
    @GlobalTransactional
    @Override
    public void deleteItem(Long itemId) {
        removeById(itemId);
        //发消息删除索引库
        log.info("发送消息到mq以同步删除商品数据至es：{}", itemId);
        rabbitMqHelper.sendMessage(ITEM_DIRECT_EXCHANGE_NAME, ITEM_DELETE_KEY, itemId);
    }
}
